/*
 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.graalvm.nativebridge;

import org.graalvm.jniutils.JNI.JNIEnv;
import org.graalvm.jniutils.JNI.JObject;
import org.graalvm.jniutils.NativeBridgeSupport;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.word.WordFactory;

/**
 * Represents a reference to an object in an isolated heap or process. A {@code Peer} is used by
 * generated {@link ForeignObject} instances to identify the corresponding object in another process
 * or isolate.
 * <p>
 * The implementation of this interface is technology-dependent and tightly coupled to the
 * {@link ForeignObject} implementation generated by the native-bridge annotation processor.
 * </p>
 *
 * @since 25.0
 */
public abstract sealed class Peer permits NativePeer, ProcessPeer, HSPeer {

    Peer() {
    }

    abstract ForeignObjectCleaner<? extends Peer> getCleaner();

    /**
     * Returns an isolate in which an object referenced by this handle exists.
     */
    public abstract Isolate<?> getIsolate();

    /**
     * Returns a handle that uniquely identifies an object in an isolated heap or process.
     */
    public abstract long getHandle();

    /**
     * Explicitly releases the object in the isolated heap that is referenced by this handle.
     * <p>
     * This method should be used only in exceptional cases. By default, the lifetime of the object
     * in the native image heap is bound to the lifetime of the {@link Peer} instance.
     * </p>
     */
    public final void release() {
        Isolate<?> isolate = getIsolate();
        ForeignObjectCleaner<? extends Peer> cleaner = getCleaner();
        if (isolate.cleaners.remove(cleaner)) {
            IsolateThread isolateThread = isolate.enter();
            try {
                cleaner.callCleanUp(isolateThread);
            } finally {
                isolateThread.leave();
            }
        }
    }

    /**
     * Creates a peer for a foreign object in a native-image isolate.
     *
     * @param nativeIsolate an isolate in which an object referenced by the handle exists.
     * @param objectHandle a handle to an object in a native image heap.
     */
    public static NativePeer create(NativeIsolate nativeIsolate, long objectHandle) {
        return new NativePeer(nativeIsolate, objectHandle);
    }

    /**
     * Creates a peer for a foreign object in another process.
     *
     * @param processIsolate an isolate in which an object referenced by the handle exists.
     * @param objectHandle a handle to an object in a native image heap.
     */
    public static ProcessPeer create(ProcessIsolate processIsolate, long objectHandle) {
        return new ProcessPeer(processIsolate, objectHandle);
    }

    /**
     * Creates a peer for a foreign object in the HotSpot heap.
     *
     * @param env a JNI thread environment API.
     * @param jObject JNI object handle.
     */
    public static HSPeer create(JNIEnv env, JObject jObject) {
        return new HSPeer(env, HSIsolate.get(), jObject);
    }

    /**
     * Creates a peer for a foreign object within the given {@code isolate}, based on the runtime
     * type of the {@code isolate}.
     *
     * <p>
     * If the provided isolate is an instance of {@link NativeIsolate}, this method returns a
     * {@link NativePeer}. If it is an instance of {@link ProcessIsolate}, it returns a
     * {@link ProcessPeer}. If it is an instance of {@link HSIsolate}, it returns a {@link HSPeer}.
     * </p>
     *
     * @param isolate the isolate in which the referenced object exists
     * @param objectHandle a handle to an object in the native image heap
     * @return an {@link Peer} corresponding to the given isolate type
     * @throws IllegalArgumentException if {@code isolate} is {@code null} or of an unsupported type
     */
    public static Peer create(Isolate<?> isolate, long objectHandle) {
        if (isolate instanceof NativeIsolate nativeIsolate) {
            return create(nativeIsolate, objectHandle);
        } else if (isolate instanceof ProcessIsolate processIsolate) {
            return create(processIsolate, objectHandle);
        } else if (isolate instanceof HSIsolate hsIsolate) {
            if (ImageSingletons.contains(NativeBridgeSupport.class)) {
                HSIsolateThread isolateThread = hsIsolate.enter();
                try {
                    return new HSPeer(isolateThread.getJNIEnv(), hsIsolate, WordFactory.pointer(objectHandle));
                } finally {
                    isolateThread.leave();
                }
            } else {
                throw new IllegalArgumentException("JNI support is not enabled.");
            }
        } else {
            throw new IllegalArgumentException("Unsupported isolate type " + isolate);
        }
    }
}
